import os.path
import re
import sqlite3
from typing import Union, Callable, List, Iterable
from urllib import parse

from scripts.Log import Log
from scripts.device.AdbDevice import AdbDevice
from scripts.device.AdbShell import AdbShell
from scripts.util.ProgressHelper import ProgressHelper
from scripts.util.RebootHelper import RebootHelper
from scripts.util.SettingsHelper import SettingsHelper
from scripts.util.Util import Util
from scripts.actions.Action import Action
from scripts.actions.Context import Context


class BackupSystemAction(Action):
    KEY = Action.register('system')
    DISPLAY_NAME = 'System'

    MISC_INCLUDE_LIST = None
    MISC_EXCLUDE_LIST = ['vold', 'profiles']

    SYSTEM_INCLUDE_LIST = None
    SYSTEM_EXCLUDE_LIST = [
        'HiJackDBHelper.db',
        'HiJackDBHelper.db-journal',
        'HiJackDBHelper.db-shm',
        'HiJackDBHelper.db-wal',
        'dropbox',
        'graphicsstats',
        'heapdump',
        'install_sessions',
        'job',
        'package_cache',
        'procstats',
        # '.masterkey',  # security related
        'cachequota.xml',
        'diskstats_cache.json',
        # 'display-manager-state.xml',  # display settings
        'entropy.dat',  # random
        'install_sessions.xml',
        'last-fstrim',
        'last-header.txt',
        'locksettings.db',
        'locksettings.db-shm',
        'locksettings.db-wal',
        'locksettings.db-journal',
        'log-files.xml',
        'ndebugsocket',  # socket file
        'overlays.xml',
        'packages-warnings.xml',
        'packages.list',
        'packages.xml',
        # 'shortcut_service.xml',
        'uiderrors.txt',
        'unsolzygotesocket',  # socket file
        # 'watchlist_settings.xml'  # security related
    ]

    VENDOR_INCLUDE_LIST = [
        'connectivity',
        'radio',
        'time',
        'wifi'
    ]
    VENDOR_EXCLUDE_LIST = [
        'wifi/hostapd/sockets',  # socket file
        'wifi/wpa/sockets',  # socket file
        'wifi/wlan_logs',  # logs
    ]

    BACKUP_DICT = {
        '/data/system': [SYSTEM_INCLUDE_LIST, SYSTEM_EXCLUDE_LIST],
        '/data/system_ce': [SYSTEM_INCLUDE_LIST, SYSTEM_EXCLUDE_LIST],
        '/data/system_de': [SYSTEM_INCLUDE_LIST, SYSTEM_EXCLUDE_LIST],
        '/data/vendor': [VENDOR_INCLUDE_LIST, VENDOR_EXCLUDE_LIST],
        '/data/vendor_ce': [VENDOR_INCLUDE_LIST, VENDOR_EXCLUDE_LIST],
        '/data/vendor_de': [VENDOR_INCLUDE_LIST, VENDOR_EXCLUDE_LIST],
        '/data/misc': [MISC_INCLUDE_LIST, MISC_EXCLUDE_LIST],
        '/data/misc_ce': [MISC_INCLUDE_LIST, MISC_EXCLUDE_LIST],  # apexdata
        '/data/misc_de': [MISC_INCLUDE_LIST, MISC_EXCLUDE_LIST]   # apexdata
    }

    SETTINGS_KEY_MAP_A9_TO_A11 = {
        SettingsHelper.pack_namespace_key('global', 'backup_charging_from_main'):
            SettingsHelper.pack_namespace_key('global', 'FS_BACK_BATT_CHARGING'),
        SettingsHelper.pack_namespace_key('global', 'low_battery_warning_level'):
            SettingsHelper.pack_namespace_key('global', 'battery_warning_level_low'),
        SettingsHelper.pack_namespace_key('global', 'critical_battery_warning_level'):
            SettingsHelper.pack_namespace_key('global', 'battery_warning_level_critical'),
        SettingsHelper.pack_namespace_key('secure', 'TOUCH_SENSITIVITY_VALUE'):
            SettingsHelper.pack_namespace_key('secure', 'TOUCH_SENSITIVITY_MODE'),
        SettingsHelper.pack_namespace_key('global', 'charging_sounds_enabled'):
            SettingsHelper.pack_namespace_key('secure', 'charging_sounds_enabled'),
        SettingsHelper.pack_namespace_key('global', 'charging_vibration_enabled'):
            SettingsHelper.pack_namespace_key('secure', 'charging_vibration_enabled')
    }

    RINGTONE_KEYS = {
        'ringtone_notification': 'notification_sound',
        'ringtone_phone': 'ringtone',
        'ringtone_alarm': 'alarm_alert'
    }

    CONFIG_FILE = 'config.json'

    MEDIA_FIX_KEYS_LIST = {
        SettingsHelper.pack_namespace_key('global', 'car_dock_sound'),
        SettingsHelper.pack_namespace_key('global', 'car_undock_sound'),
        SettingsHelper.pack_namespace_key('global', 'desk_dock_sound'),
        SettingsHelper.pack_namespace_key('global', 'desk_undock_sound'),
        SettingsHelper.pack_namespace_key('global', 'lock_sound'),
        SettingsHelper.pack_namespace_key('global', 'low_battery_sound'),
        SettingsHelper.pack_namespace_key('global', 'trusted_sound'),
        SettingsHelper.pack_namespace_key('global', 'unlock_sound'),
        SettingsHelper.pack_namespace_key('global', 'wireless_charging_started_sound')
    }

    def __init__(self, context: Context):
        super().__init__(BackupSystemAction.KEY, BackupSystemAction.DISPLAY_NAME, context)

    def _on_backup(self, progress: Union[None, Callable[[int, int, str], None]]):
        device = self.wait_for_boot_up_ready()
        with device.shell() as shell:
            settings = SettingsHelper(os.path.join(self.data_dir(), BackupSystemAction.CONFIG_FILE))

            # progress
            prog = ProgressHelper(progress, len(BackupSystemAction.BACKUP_DICT))

            # start user
            user_infos = Util.dump_pkg_infos(shell)
            user_list = user_infos.keys()
            Action.start_users(user_list, shell=shell)
            Action.wait_for_filesystem_ready(user_list, shell=shell)

            # stop android before backup data files
            Log.d('stop android before backup data files')
            shell.android_stop()  # stop android first

            # do file backup
            Log.d('backup system files')
            for device_path in BackupSystemAction.BACKUP_DICT:
                prog.on_message(device_path).inc()  # on progress
                include_exclude = BackupSystemAction.BACKUP_DICT[device_path]
                BackupSystemAction._check_before_backup(device_path, include_exclude, user_list, shell)
                self.backup_path(device_path=device_path,
                                 host_dir=self.data_dir(),
                                 device=device,
                                 shell=shell,
                                 compress=True,
                                 compress_include=include_exclude[0],
                                 compress_exclude=include_exclude[1])
                Log.d('backup: %s' % device_path)

            # backup HiJackDBHelper.db
            Log.d('backup: hijackdb')
            self._backup_hijackdb(device, shell)

            # start android after backup data files
            Log.d('start android after backup data files')
            shell.android_start()  # start android

            # start users again
            Action.start_users(user_list, shell=shell)
            Action.wait_for_filesystem_ready(user_list, shell=shell)

            # do settings backup
            prog.on_message('Backup more settings...')
            android_version = self.android_version()
            Log.d('backup settings: settings_%d_%s' % (0, 'global'))
            BackupSystemAction._backup_settings(settings, 0, 'global', shell, android_version)
            for user in user_list:
                for namespace in ('system', 'secure'):
                    Log.d('backup settings: settings_%d_%s' % (user, namespace))
                    BackupSystemAction._backup_settings(settings, user, namespace, shell, android_version)

            # map settings
            BackupSystemAction._map_settings_from_a9_to_a11(settings, user_list)

            Log.d('backup settings: bluetooth_on')
            settings.put('bluetooth_on', shell.is_bluetooth_on())

            Log.d('backup settings: density')
            settings.put('density', shell.wm_get_density())

            # Log.d('backup settings: hotspot auto turn off')
            # self._backup_hotspot_auto_turn_off(settings, shell)

            Log.d('backup settings: timezone')
            settings.put('timezone', shell.getprop('persist.sys.timezone'))

            usb_mode = shell.getprop('persist.sys.usb.priority')
            if usb_mode != '':
                Log.d('backup settings: usb connection mode')
                settings.put('persist.sys.usb.priority', usb_mode)

            # backup ringtone
            self._backup_ringtone(settings, user_list, device, shell)

            # backup media settings
            self._backup_media_settings(settings, user_list)

            settings.save()

            prog.on_message('Done [%s]' % self.duration_time_str())

    def _on_restore(self, progress: Union[None, Callable[[int, int, str], None]]):
        device = self.wait_for_boot_up_ready()
        with device.shell() as shell:
            settings = SettingsHelper(os.path.join(self.data_dir(), BackupSystemAction.CONFIG_FILE))
            host_files = set(os.listdir(self.data_dir())) - {BackupSystemAction.CONFIG_FILE, }
            host_paths = {os.path.join(self.data_dir(), name) for name in host_files}
            host_paths = set(filter(lambda p: not os.path.isdir(p), host_paths))

            # progress
            prog = ProgressHelper(progress, len(host_paths))

            # start user
            user_infos = Util.dump_pkg_infos(shell)
            user_list = user_infos.keys()
            Action.start_users(user_list, shell=shell)
            Action.wait_for_filesystem_ready(user_list, shell=shell)

            # stop android before restoring data files
            Log.d('stop android before restoring data files')
            shell.android_stop()  # stop android first

            # restore system data
            Log.d('restore system files')
            for host_path in host_paths:
                device_path = Action.host_path_to_device_path(host_path, compress=True)
                prog.on_message(device_path).inc()
                shell.wait_for_path_ready(device_path)
                self.restore_path(host_path=host_path,
                                  device=device,
                                  shell=shell,
                                  compress=True,
                                  clear_copy=False)
                Log.d('restore: %s' % os.path.basename(host_path))

            # restore HiJackDBHelper.db
            Log.d('restore: hijackdb')
            self._restore_hijackdb(device, shell)

            # # restore hotspot auto turn off
            # Log.d('restore settings: hotspot auto turn off')
            # self._restore_hotspot_auto_turn_off(settings, device, shell)

            # start android after restored data files
            Log.d('start android after restored data files')
            shell.android_start()  # start android

            # safe reboot
            prog.on_message('Reboot to apply system changes...')
            Log.d('safe reboot to apply system changes')

            _, device, shell = RebootHelper(self).safe_reboot(device, shell)

        # wait for device online
        device = self.wait_for_boot_up_ready()
        with device.shell() as shell:
            # start users again
            Action.start_users(user_list, shell=shell)
            Action.wait_for_filesystem_ready(user_list, shell=shell)

            # restore settings
            prog.on_message('Restore settings...')
            Log.d('restore setting: settings_%d_%s' % (0, 'global'))
            self._restore_settings(settings, 0, 'global', shell)
            for user in user_list:
                for namespace in ('system', 'secure'):
                    Log.d('restore setting: settings_%d_%s' % (user, namespace))
                    self._restore_settings(settings, user, namespace, shell)

            Log.d('restore settings: bluetooth_on')
            shell.set_bluetooth_on(settings.get('bluetooth_on'))

            Log.d('restore settings: density')
            shell.wm_set_density(settings.get('density'))

            # restore timezone
            timezone = settings.get('timezone')
            if timezone is not None:
                Log.d('restore settings: timezone [%s]' % timezone)
                shell.setprop('persist.sys.timezone', timezone)

            usb_mode = settings.get('persist.sys.usb.priority')
            if usb_mode is not None:
                Log.d('restore settings: usb connection mode')
                shell.setprop('persist.sys.usb.priority', usb_mode)
                shell.exec('echo %s > /sys/class/power_supply/battery/usb_priority_is_cradle' % usb_mode)

            # restore ringtone
            self._restore_ringtone(settings, user_list, device, shell)

            # restore media settings
            self._restore_media_settings(settings, user_list, shell)

            # safe reboot
            prog.on_message('Reboot to apply system settings changes...')
            Log.d('safe reboot to apply system settings changes')
            _, device, shell = RebootHelper(self).safe_reboot(device, shell)

            prog.on_message('Done [%s]' % self.duration_time_str())

    @staticmethod
    def _backup_settings(settings: SettingsHelper, user: int, namespace: str, shell: AdbShell,
                         android_version: int = 11):
        if android_version >= 11:
            settings.put(SettingsHelper.to_key(user, namespace),
                         shell.settings_dump_a11(namespace, user=user))
        else:
            setting_values = shell.settings_dump_a9(namespace, user=user)
            settings.put(SettingsHelper.to_key(user, namespace),
                         setting_values)

    @staticmethod
    def _restore_settings(settings: SettingsHelper, user: int, namespace: str, shell: AdbShell):
        value = settings.get(SettingsHelper.to_key(user, namespace), None)
        if value:
            shell.settings_restore(namespace, value, user=user)

    def _backup_hotspot_auto_turn_off(self, settings: SettingsHelper, shell: AdbShell):
        if self.android_version() < 11:
            global_settings = settings.get(SettingsHelper.to_key(0, 'global'))
            value = global_settings['soft_ap_timeout_enabled']
            del global_settings['soft_ap_timeout_enabled']  # remove this setting
            enabled = value == '1'
            Log.d('got AutoShutdownEnabled: %s' % enabled)
            settings.put('AutoShutdownEnabled', enabled)
        else:
            content = shell.cat('/data/misc/apexdata/com.android.wifi/WifiConfigStoreSoftAp.xml')
            lines = content.split('\n')
            pattern = re.compile(r'\s*<boolean\s+name="AutoShutdownEnabled"\s+value="(true|false|)"\s*/>\s*')
            for line in lines:
                matched = pattern.fullmatch(line)
                if matched:
                    enabled = matched.group(1) == 'true'
                    Log.d('got AutoShutdownEnabled: %s' % enabled)
                    settings.put('AutoShutdownEnabled', enabled)
                    break

    def _restore_hotspot_auto_turn_off(self, settings: SettingsHelper, device: AdbDevice, shell: AdbShell):
        enabled = settings.get('AutoShutdownEnabled', False)
        if self.android_version() < 11:
            shell.settings_put('global', 'soft_ap_timeout_enabled', '1' if enabled else '0')
        else:
            # get content of xml and modify
            device_path = '/data/misc/apexdata/com.android.wifi/WifiConfigStoreSoftAp.xml'
            # make temp file on host
            temp_dir = os.path.join(self.data_dir(), 'temp')
            host_path = os.path.join(temp_dir, 'WifiConfigStoreSoftAp.xml')
            os.makedirs(temp_dir, exist_ok=True)
            config_xml = Action.FileTransferItem(
                device_path,
                host_path,
                compress=False,
                clear_copy=False
            )

            # pull from device
            Action.pull_file(device=device,
                             file_list=config_xml,
                             shell=shell)

            # load file
            with open(host_path, 'r') as file:
                content = file.read()

            # modify file
            pattern =\
                re.compile(r'([\s\S]*<boolean\s+name="AutoShutdownEnabled"\s+value=")(true|false|)("\s*/>[\s\S]*)')
            matched = pattern.fullmatch(content)
            if matched:
                content = '%s%s%s' % (matched.group(1), 'true' if enabled else 'false', matched.group(3))

            # update file
            with open(host_path, 'w', newline='') as file:
                file.write(content)

            # push to device
            Action.push_file(device=device,
                             file_list=config_xml,
                             shell=shell)
            shell.chown('system', 'system', device_path, recursive=False)
            shell.chmod('600', device_path, recursive=False)

    @staticmethod
    def _check_before_backup(device_path: str, include_exclude: List[Union[None, List[str]]], user_list,
                             shell: AdbShell):
        if device_path == '/data/system':
            # exclude settings provider settings
            for user in user_list:
                # exclude SettingsProvider data
                for namespace in ('global', 'system', 'secure'):
                    exclude_path = 'users/%d/settings_%s.xml' % (user, namespace)
                    path = '/data/system/%s' % exclude_path
                    if shell.path_exists(path):
                        if include_exclude[1] is None:
                            include_exclude[1] = [exclude_path, ]
                        else:
                            include_exclude[1].append(exclude_path)

                # exclude appwidgets.xml
                appwidgets_xml_path = '/data/system/users/%d/appwidgets.xml' % user
                if shell.path_exists(appwidgets_xml_path):
                    if include_exclude[1] is None:
                        include_exclude[1] = [appwidgets_xml_path[len(device_path) + 1:], ]
                    else:
                        include_exclude[1].append(appwidgets_xml_path[len(device_path) + 1:])
        elif device_path == '/data/system_ce':
            for user in user_list:
                shotcut_server_path = '/data/system_ce/%d/shortcut_service' % user
                if shell.path_exists(shotcut_server_path):
                    if include_exclude[1] is None:
                        include_exclude[1] = [shotcut_server_path[len(device_path) + 1:], ]
                    else:
                        include_exclude[1].append(shotcut_server_path[len(device_path) + 1:])

    @staticmethod
    def _map_settings_from_a9_to_a11(settings: SettingsHelper, user_list: Iterable[int]):
        for key_map_a9 in BackupSystemAction.SETTINGS_KEY_MAP_A9_TO_A11:
            namespace_a9, settings_key_a9 = SettingsHelper.unpack_namespace_key(key_map_a9)
            namespace_a11, settings_key_a11 = SettingsHelper.unpack_namespace_key(
                BackupSystemAction.SETTINGS_KEY_MAP_A9_TO_A11[key_map_a9])
            for user in user_list:
                # prepare a9
                host_settings_key_a9 = SettingsHelper.to_key(user, namespace_a9)
                settings_values_a9 = settings.get(host_settings_key_a9)
                if settings_values_a9 is None:
                    continue
                if settings_key_a9 not in settings_values_a9:
                    continue

                # prepare a11
                host_settings_key_a11 = SettingsHelper.to_key(user, namespace_a11)
                settings_values_a11 = settings.get(host_settings_key_a11)
                if settings_values_a11 is None:
                    settings_values_a11 = dict()
                    settings.put(host_settings_key_a11, settings_values_a11)

                # map key
                Log.d('map key from [%s:%s] to [%s:%s]' % (namespace_a9, settings_key_a9,
                                                           namespace_a11, settings_key_a11))
                settings_values_a11[settings_key_a11] = settings_values_a9[settings_key_a9]
                del settings_values_a9[settings_key_a9]

    def _backup_hijackdb(self, device: AdbDevice, shell: AdbShell):
        # generate host dir
        host_dir = os.path.join(self.data_dir(), 'hijackdb')
        if not os.path.exists(host_dir):
            os.makedirs(host_dir, exist_ok=True)

        # get file list of HiJackDBHelper from device
        db_paths = shell.find('/data/system/HiJackDBHelper.db*', 0, 0)
        if '/data/system/HiJackDBHelper.db' not in db_paths:
            Log.w('ignore fix HiJackDBHelper.db due to db not exists')
            os.removedirs(host_dir)
            return

        # pull db from device
        db_files = [Action.FileTransferItem(
            device_path=path,
            host_path=os.path.join(host_dir, os.path.basename(path)),
            compress=False,
            clear_copy=False
        ) for path in db_paths]
        self.pull_file(device, db_files, shell=shell)

        # check device model
        if self.device_model() in {'PM90', }:
            Log.d('unnecessary to fix hijackdb due to device model is %s' % self.device_model())
            return

        # get Android version
        if self.android_version() >= 11:
            Log.d('unnecessary to fix hijackdb due to android version is %d' % self.android_version())
            return

        # fix db for android version <= 9
        # check whether column should be added
        db_path = os.path.join(host_dir, 'HiJackDBHelper.db')
        conn = sqlite3.connect(database=db_path)
        cursor = conn.cursor()
        cursor.execute('PRAGMA table_info(HiJack)')
        columns = {col[1] for col in cursor.fetchall()}
        cursor.close()

        # add column if not exists
        if '_marco_keys' in columns:
            Log.w('ignore fix HiJackDBHelper.db due column "_marco_keys" already exists')
            return
        conn.execute('ALTER TABLE HiJack ADD COLUMN _marco_keys text not null default ""')
        conn.commit()
        conn.close()

    def _restore_hijackdb(self, device: AdbDevice, shell: AdbShell):
        # generate host dir
        host_dir = os.path.join(self.data_dir(), 'hijackdb')
        if not os.path.exists(host_dir):
            Log.w('ignore restoring hijackdb due to folder not exists')
            return

        # remove remote db
        db_paths = shell.find('/data/system/HiJackDBHelper.db*', 0, 0)
        for db_path in db_paths:
            shell.rm(db_path, recursive=False)

        # get file list of HiJackDBHelper from host
        db_names = list(filter(lambda n: n.startswith('HiJackDBHelper.db'), os.listdir(host_dir)))
        db_files = [Action.FileTransferItem(
            device_path='/data/system/%s' % name,
            host_path=os.path.join(host_dir, name),
            compress=False,
            clear_copy=False
        ) for name in db_names]
        Action.push_file(device, db_files, shell=shell)

        # chown & chmod
        db_paths = {'/data/system/%s' % name for name in db_names}
        for path in db_paths:
            shell.chown(user='system',
                        group='system',
                        path=path,
                        recursive=False)
            shell.chmod(mode='660',
                        path=path,
                        recursive=False)

    def _backup_ringtone(self, settings: SettingsHelper, user_list: Iterable[int], device: AdbDevice, shell: AdbShell):
        android_version = self.android_version()
        host_dir = os.path.join(self.data_dir(), 'ringtone')
        if not os.path.exists(host_dir):
            os.makedirs(host_dir, exist_ok=True)

        # backup internal database
        for user in user_list:
            host_user_dir = os.path.join(host_dir, str(user))
            if not os.path.exists(host_user_dir):
                os.makedirs(host_user_dir, exist_ok=True)

            # get databases dir on device
            device_db_dir = '/data/user/%d/com.android.providers.media/databases' % user\
                if android_version == 9\
                else '/data/user/%d/com.android.providers.media.module/databases' % user

            # get database file list & backup
            device_files = shell.find('%s/internal.db*' % device_db_dir, min_depth=0, max_depth=0)
            for device_file in device_files:
                Action.pull_file(device, Action.FileTransferItem(
                    device_path=device_file,
                    host_path=os.path.join(host_user_dir, os.path.basename(device_file)),
                    compress=False,
                    clear_copy=False
                ), shell=shell)

        # backup ringtone settings
        for key in BackupSystemAction.RINGTONE_KEYS:
            settings_key = BackupSystemAction.RINGTONE_KEYS[key]
            for user in user_list:
                system_settings = settings.get(SettingsHelper.to_key(user, 'system'))
                if settings_key in system_settings:
                    url = system_settings[settings_key]
                    del system_settings[settings_key]  # remove settings
                    Log.d('backup ringtone: [%d:%s] %s' % (user, settings_key, url))
                    settings.put('%d:%s' % (user, key), url)
                else:
                    Log.d('backup ringtone: [%d:%s] deleted' % (user, settings_key))

    def _restore_ringtone(self, settings: SettingsHelper, user_list: Iterable[int], device: AdbDevice, shell: AdbShell):
        android_version = self.android_version()
        host_dir = os.path.join(self.data_dir(), 'ringtone')
        if not os.path.exists(host_dir):
            Log.d('ignore restoring ringtone due to data not exists')
            return

        # pull internal database
        host_temp_dir = os.path.join(host_dir, 'temp')
        for user in user_list:
            host_temp_user_dir = os.path.join(host_temp_dir, str(user))
            if not os.path.exists(host_temp_user_dir):
                os.makedirs(host_temp_user_dir, exist_ok=True)

            # get databases dir on device
            device_db_dir = '/data/user/%d/com.android.providers.media/databases' % user\
                if android_version == 9\
                else '/data/user/%d/com.android.providers.media.module/databases' % user

            # get database file list & backup
            device_files = shell.find('%s/internal.db*' % device_db_dir, min_depth=0, max_depth=0)
            for device_file in device_files:
                Action.pull_file(device, Action.FileTransferItem(
                    device_path=device_file,
                    host_path=os.path.join(host_temp_user_dir, os.path.basename(device_file)),
                    compress=False,
                    clear_copy=False
                ), shell=shell)

        # load backup
        for user in user_list:
            for key in BackupSystemAction.RINGTONE_KEYS:
                settings_key = BackupSystemAction.RINGTONE_KEYS[key]
                backup_key = '%d:%s' % (user, key)
                original_url = settings.get(backup_key)
                if original_url is None:
                    shell.settings_delete('system', settings_key, user, android_version)
                    Log.d('restore: [%s] deleted' % backup_key)
                else:
                    # get original id and db paths
                    original_url_parse = parse.urlparse(original_url)
                    original_id = os.path.basename(original_url_parse.path)
                    original_db = os.path.join(host_dir, str(user), 'internal.db')
                    current_db = os.path.join(host_temp_dir, str(user), 'internal.db')

                    # get path from original db
                    conn = sqlite3.connect(original_db)
                    cursor = conn.cursor()
                    cursor.execute('SELECT _data FROM files WHERE _id=%s' % original_id)
                    row = cursor.fetchone()
                    cursor.close()
                    conn.close()
                    if row is None:
                        continue  # ignore due to id not found from the database
                    original_media_path: str = row[0]

                    # query current db
                    current_media_path = original_media_path.replace('/system/media/', '/system/product/media/')
                    conn = sqlite3.connect(current_db)
                    cursor = conn.cursor()
                    cursor.execute('SELECT _id,title FROM files WHERE _data="%s"' % current_media_path)
                    row = cursor.fetchone()
                    cursor.close()
                    conn.close()
                    if row is None:
                        continue  # ignore due to ringtone not found from new database

                    # generate new url
                    current_id = row[0]
                    current_title = row[1]
                    current_url = 'content://media/internal/audio/media/%s?title=%s&canonical=1' %\
                                  (current_id, parse.quote(current_title))
                    shell.settings_put('system', settings_key, current_url, user)

                    Log.d('restore: [%s] "%s" to "%s"' % (backup_key, original_url, current_url))

        # remove temp dir
        Util.rmdirs(host_temp_dir)

    def _backup_media_settings(self, settings: SettingsHelper, user_list: Iterable[int]):
        for packed_key in BackupSystemAction.MEDIA_FIX_KEYS_LIST:
            namespace, key = SettingsHelper.unpack_namespace_key(packed_key)

            for user in user_list:
                settings_values = settings.get(SettingsHelper.to_key(user, namespace))
                if settings_values is None or key not in settings_values:
                    continue

                value = settings_values[key]\
                    if self.android_version() >= 11\
                    else settings_values[key].replace('/system/media/audio/', '/product/media/audio/')
                settings.put(SettingsHelper.pack_user_namespace_key(user, namespace, key), value)
                del settings_values[key]

    def _restore_media_settings(self, settings: SettingsHelper, user_list: Iterable[int], shell: AdbShell):
        for packed_key in BackupSystemAction.MEDIA_FIX_KEYS_LIST:
            namespace, key = SettingsHelper.unpack_namespace_key(packed_key)

            for user in user_list:
                settings_key = SettingsHelper.pack_user_namespace_key(user, namespace, key)
                settings_value = settings.get(settings_key)
                if settings_value is None:
                    continue

                if self.android_version() < 11:
                    settings_value.replace('/product/media/audio/', '/system/media/audio/')

                if not shell.path_exists(settings_value):
                    Log.d('ignore unknown path [%s] for [%s]' % (settings_value, settings_key))
                    continue
                shell.settings_put(namespace, key, settings_value, user)
